package com.zzyl.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.zzyl.constant.CacheConstants;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.properties.AlertInfoProperties;
import com.zzyl.socket.WebSocketServer;
import com.zzyl.vo.DeviceDataVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class AlertJob {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private AlertInfoProperties alertInfoProperties;

    @Autowired
    private AlertDataMapper alertDataMapper;

    @Autowired
    private WebSocketServer webSocketServer;

    @Scheduled(fixedDelay = 10000)
    public void deviceAlerHandler(){
        //1. 查询所有报警规则
        List<AlertRule> alertRules = alertRuleMapper.selectAll();
        if (CollectionUtil.isEmpty(alertRules)){
            return;
        }
        //2. 获取所有的报警数据
        List<Object> values = redisTemplate.opsForHash().values(CacheConstants.IOT_DEVICE_LAST_DATA);
        if (CollectionUtil.isEmpty(values)){
            return;
        }
        //3. 对报警数据进行格式化处理
        List<DeviceDataVo> deviceDataVoList = values.stream().map(data -> JSONUtil.toList(data.toString(), DeviceDataVo.class)).flatMap(deviceDataList -> deviceDataList.stream()).collect(Collectors.toList());
        //4. 遍历每一条报警规则,匹配需要执行的规则
        deviceDataVoList.forEach(deviceDataVo -> matchAndHandlerAlertRule(deviceDataVo,alertRules));
    }

    private void matchAndHandlerAlertRule(DeviceDataVo deviceDataVo, List<AlertRule> alertRules) {
        //1. 判断设备上报数据的时间,是否在一分钟之内
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();
        if (alarmTime.isBefore(LocalDateTime.now().minusMinutes(1))){
            return;
        }
        //2. 从所有的报警规则中,找到适用与当前数据的报警规则
        List<AlertRule> ruleList = alertRules.stream()
                //过滤产品
                .filter(alertRule -> StrUtil.equals(alertRule.getProductKey(), deviceDataVo.getProductKey()))
                //过滤指标
                .filter(alertRule -> StrUtil.equals(alertRule.getFunctionId(), deviceDataVo.getFunctionId()))
                //过滤设备
                .filter(alertRule -> StrUtil.equals(alertRule.getIotId(), deviceDataVo.getIotId()) || StrUtil.equals(alertRule.getIotId(), "-1"))
                //采集过滤之后的数据
                .collect(Collectors.toList());
        if (CollectionUtil.isEmpty(ruleList)){
            return;
        }
        //3. 遍历规则,判断是否达到报警条件
        ruleList.forEach(alertRule -> {
            boolean isNeed = isNeedHandleAlert(alertRule, deviceDataVo);
            if (isNeed){
                handleAlert(alertRule, deviceDataVo);
            }
        });
    }

    private void handleAlert(AlertRule alertRule, DeviceDataVo deviceDataVo) {
        //1. 清空持续周期次数
        clearTriggerCount(deviceDataVo, alertRule);
        //2. 保持沉默周期标识
        setSilentFlag(alertRule, deviceDataVo);
        //3. 获取报警通知人
        List<Long> userIds;
        if (deviceDataVo.getPhysicalLocationType()==-1){
            //3.1 设备绑定老人
            userIds = deviceMapper.queryNursingIdByIotIdWithElder(deviceDataVo.getIotId());
        }else if (deviceDataVo.getPhysicalLocationType()==2){
            //3.2 设备绑定老人床位
            userIds = deviceMapper.queryNursingIdByIotIdWithBed(deviceDataVo.getIotId());
        }else {
            //3.3 固定设备
            userIds = userRoleMapper.queryUserIdByRoleName(alertInfoProperties.getDeviceMaintainerRole());
        }
        // 所有报警通知,都要发送给超级管理员
        List<Long> managerIds = userRoleMapper.queryUserIdByRoleName(alertInfoProperties.getManagerRole());
        userIds = CollectionUtil.addAll(userIds, managerIds).stream().distinct().collect(Collectors.toList());
        //4. 为报警通知人保存报警消息
        insertAlertData(alertRule, deviceDataVo, userIds);
    }

    private void insertAlertData(AlertRule alertRule, DeviceDataVo deviceDataVo, List<Long> userIds) {
        //1. 封装基础的报警数据到实体类
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);
        alertData.setAlertRuleId(alertRule.getId());
        String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警", alertRule.getFunctionName(), alertRule.getOperator(), alertRule.getValue(), alertRule.getDuration());
        alertData.setAlertReason(alertReason);
        alertData.setType(alertRule.getAlertDataType());
        alertData.setStatus(0);
        //2. 遍历用户ID集合,为每一个需要接收报警信息的用户保存报警数据
        List<AlertData> alertDataList = userIds.stream().map(userId -> {
            //基于初始对象, 获取新对象
            //AlertData newAlertData = BeanUtil.toBean(alertData, AlertData.class);
            //深克隆 : 创建一个跟原有对象数据属性完全相同的新对象
            //浅克隆 : 创建一个对象, 新对象和旧对象的地址值相同
            AlertData clone = ObjectUtil.clone(alertData);
            clone.setUserId(userId);
            return clone;
        }).collect(Collectors.toList());
        alertDataMapper.batchInsert(alertDataList);

        // 获取需要推送的用户id列表
        List<Long> list = alertDataList.stream().map(AlertData::getUserId).collect(Collectors.toList());

        //发送websocket消息
        websocketNotify(alertRule, alertDataList.get(0), list);
    }

    /**
     * 发送websocket通知
     *
     * @param alertRule 报警规则
     * @param alertData 报警数据
     */
    private void websocketNotify(AlertRule alertRule, AlertData alertData, List<Long> userIds) {
        // websocket通知：弹窗消息
        AlertNotifyDto alertNotifyDto = BeanUtil.toBean(alertData, AlertNotifyDto.class);
        alertNotifyDto.setFunctionName(alertRule.getFunctionName());
        alertNotifyDto.setAlertDataType(alertData.getType());
        alertNotifyDto.setNotifyType(1);
        //给指定消费者发送消息
        webSocketServer.sendMessageToConsumer(alertNotifyDto, userIds);
    }

    private void setSilentFlag(AlertRule alertRule, DeviceDataVo deviceDataVo) {
        String key = CacheConstants.ALERT_SILENT_CYCLE + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        redisTemplate.opsForValue().set(key, deviceDataVo.getDataValue(), Duration.ofMinutes(alertRule.getAlertSilentPeriod()));
    }

    private void clearTriggerCount(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        String key = CacheConstants.ALERT_TRIGGER_COUNT + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        redisTemplate.delete(key);
    }

    private boolean isNeedHandleAlert(AlertRule alertRule, DeviceDataVo deviceDataVo) {
        //1. 判断是否在生效时间内
        boolean period = isInEffectivePeriod(deviceDataVo, alertRule);
        if (!period){
            return false;
        }
        //2. 判断数据阈值
        boolean threshold = isThreshold(deviceDataVo, alertRule);
        if (!threshold){
            clearTriggerCount(deviceDataVo, alertRule);
            return false;
        }
        //3. 判断持续周期
        boolean duration = isDuration(deviceDataVo, alertRule);
        if (!duration){
            return false;
        }
        //4. 判断沉默周期
        boolean silent = isSilentPeriod(deviceDataVo, alertRule);
        if (silent){
            clearTriggerCount(deviceDataVo, alertRule);
            return false;
        }

        return true;
    }

    private boolean isDuration(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        Integer duration = alertRule.getDuration();
        String triggerCountKey = CacheConstants.ALERT_TRIGGER_COUNT + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        Long count = redisTemplate.opsForValue().increment(triggerCountKey);
        if (ObjectUtil.equals(duration,count.intValue())){
            return true;
        }
        return false;
    }

    private boolean isSilentPeriod(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        String silentKey = CacheConstants.ALERT_SILENT_CYCLE + deviceDataVo.getIotId() + ":" + deviceDataVo.getFunctionId() + ":" + alertRule.getId();
        return redisTemplate.hasKey(silentKey);
    }

    private boolean isThreshold(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        Float value = alertRule.getValue();
        String operator = alertRule.getOperator();
        String dataValue = deviceDataVo.getDataValue();
        if ((">=".equals(operator)&& Float.parseFloat(dataValue) >= value)||("<".equals(operator)&& Float.parseFloat(dataValue) < value)){
            return true;
        }
        return false;
    }

    private boolean isInEffectivePeriod(DeviceDataVo deviceDataVo, AlertRule alertRule) {
        String alertEffectivePeriod = alertRule.getAlertEffectivePeriod();
        String[] strings = alertEffectivePeriod.split("~");
        LocalTime startTime = LocalTime.parse(strings[0], DateTimeFormatter.ofPattern("HH:mm:ss"));
        LocalTime endTime = LocalTime.parse(strings[1], DateTimeFormatter.ofPattern("HH:mm:ss"));
        LocalTime time = deviceDataVo.getAlarmTime().toLocalTime();
        if (time.isBefore(startTime) || time.isAfter(endTime)){
            return false;
        }
        return true;
    }

}
